package com.wr.sqlSession;

import com.wr.executer.Executer;
import com.wr.executer.SimpleExecuter;
import com.wr.pojo.Configuration;
import com.wr.pojo.MappedStatement;

import java.lang.reflect.*;
import java.util.List;

/**
 * @author 王瑞
 * @Description :
 * @date 2020/1/29 - 20:52
 */
public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object... args) throws Exception {
        Executer Executer = new SimpleExecuter();
        MappedStatement mappedStatement = configuration.getMap().get(statementId);
        List<Object> list = Executer.query(configuration, mappedStatement, args);
        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statmentId, Object... args) throws Exception {

        List<Object> objects = selectList(statmentId, args);
        if (objects.size() == 1) {
            return (T) objects.get(0);
        } else {
            throw new RuntimeException("查询结果为空或者多个");
        }
    }

    /*
     *
     * 使用jdk动态代理 来生成dao层的代理对象
     *
     *  注意：1.当使用代理对象来调用对象方法时 该程序在执行时一定会走代理对象的invoke方法
     *        2.使用动态代理方式时 mapper.xml中的namespace的值必须是当前映射dao实体的全路径   而id的值要和对应dao方法名称保持一致
     *
     * */
    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        Class<?>[] classes = {mapperClass};
        //Proxy 是Jdk提供的一个处理代理的对象
        Object instance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), classes, new InvocationHandler() {
            //invoke方法三个参数的说明：proxy是当前代理对象的引用   method是当前被调用方法的引用   args是method方法传递的参数
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //无论如何封装 底层都是要走jdbc的    这里可以根据不同情况 来调用当前类的selectList或者selectOne方法来完成操作
                //1.准备参数  这里要拿到statemenId  但是在这个方法里 是拿不到这个值的
                // 所以用动态代理方式时 mapper.xml中的namespace的值必须是当前映射dao实体的全路径   而id的值要和对应dao方法名称保持一致
                //这时的statemenId 其实就是dao接口全限定名.方法名了  而接口全限定名和方法名是可以通过反射来获得的 这样就可以拼接出一个statmentId了

                String methodName = method.getName(); //这里 拿到当前方法的方法名称    如findAll
                String className = method.getDeclaringClass().getName();  //拿到当前方法所在的类的全限定名  如  com.wr.dao.IUserDao
                String statmentId = className + "." + methodName;
                Type genericReturnType = method.getGenericReturnType();  //获取当前方法的返回值类型
                if (genericReturnType instanceof ParameterizedType) {      //这里简单的判断一下当前的返回值是否进行了泛型类型参数化  主要是用来判断该调用哪个方法来返回数据
                    List<Object> list = selectList(statmentId, args);
                    return list;
                } else {
                    Object o = selectOne(statmentId, args);
                    return o;
                }
            }
        });

        return (T) instance;
    }
}

